#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>

template <typename T>
class MemoryPool
{
private:
    size_t m_size;
    void *m_memory;
    T *m_freeList;

public:
    MemoryPool(std::size_t blockSize = 1024)
    {
        m_size = blockSize;
        m_memory = malloc(m_size * sizeof(T));

        /**
         * The reinterpret_cast operator is used to cast a pointer from one type to another.
         * the m_memory member variable of type char * is cast to a pointer of type T *,
         * where T is the type of objects stored in the pool. The cast allows the m_freeList variable to be
         * initialized with a pointer to the first element of the memory block.
         * The m_freeList variable is used to keep track of the available objects in the pool that can be allocated.
         * It points to the first available object in the pool, or it is nullptr if no objects are available.
         */

        m_freeList = reinterpret_cast<T *>(m_memory);
        T *current = reinterpret_cast<T *>(m_memory);
        T *end = current + m_size * sizeof(T);

        while (current < end)
        {
            /**
             * The reinterpret_cast function is used to cast the memory addresses to a different data type,
             * a pointer to a pointer to a T object (T **).
             * the line *reinterpret_cast<T **>(current) = reinterpret_cast<T *>(current + sizeof(T));
             * is creating a linked list structure within the memory pool.
             * The memory pool stores the memory blocks in a linked list structure,
             * where each memory block points to the next memory block in the list.
             * The code is linking the current memory block to the next memory block
             * by storing the address of the next block in the current block using a pointer.
             * This way, when a memory block is freed, it can be added to the linked list and the memory pool
             * can quickly find a free memory block when it needs to allocate more memory.
             */
            *reinterpret_cast<T **>(current) = reinterpret_cast<T *>(current + sizeof(T));
            current = reinterpret_cast<T *>(current + sizeof(T));
        }
    }

    ~MemoryPool() { free(m_memory); }

    /**
     * attempts to allocate a new element of type T from the memory pool.
     * If the memory pool is empty and there are no more elements left to allocate,
     * it returns a null pointer.
     */

    /**
     * it first retrieves the next available element in the pool by reading the value of m_freeList.
     * The value of m_freeList is then updated to point to the next available element in the memory pool
     * by casting m_freeList to a T**, dereferencing it, and storing the result back in m_freeList.
     * Finally, the function returns the previously retrieved T object,
     * which is the next available element in the pool.
     */
    T *allocate()
    {
        if (m_freeList == nullptr)
        {
            return nullptr;
        }
        T *result = m_freeList;
        m_freeList = *reinterpret_cast<T **>(m_freeList);
        return result;
    }

    /**
     * simply sets the pointer to the free list (stored in the m_freeList member variable)
     * as the first value of the memory block pointed to by p,
     * and then updates m_freeList to point to p.
     * This effectively adds the memory block pointed to by p back to the free list,
     * making it available for future allocations.
     */
    void deallocate(T *p)
    {
        *reinterpret_cast<T **>(p) = m_freeList;
        m_freeList = p;
    }
};

template <typename T>
class ObjectMemoryPool
{
private:
    static constexpr int pool_size = 50;

    struct Slot
    {
        T object;
        Slot *next;
        bool is_active;
        std::aligned_storage_t<sizeof(T), alignof(T)> data;
    };

    Slot *m_freeList;
    Slot m_memory[pool_size];

    Slot *allocate_slot()
    {
        if (m_freeList == nullptr)
        {
            return &m_memory[0];
        }
        Slot *result = m_freeList;
        m_freeList = m_freeList->next;
        return result;
    }

    void deallocate_slot(Slot *slot)
    {
        slot->next = m_freeList;
        m_freeList = slot;
    }

public:
    ObjectMemoryPool()
    {
        for (int i = 0; i < pool_size - 1; i++)
        {
            m_memory[i].next = &m_memory[i + 1];
        }
        m_memory[pool_size - 1].next = nullptr;
        m_freeList = &m_memory[0];
    }

    ~ObjectMemoryPool()
    {
    }

    T *allocate()
    {
        Slot *slot = allocate_slot();
        return reinterpret_cast<T *>(&slot->data);
    }

    void deallocate(T *ptr)
    {
        Slot *slot = reinterpret_cast<Slot *>(reinterpret_cast<char *>(ptr) - offsetof(Slot, data));
        deallocate_slot(slot);
    }
};
